import type { MaybePromise, SchemaTypes } from '@pothos/core';
import type { GraphQLResolveInfo } from 'graphql';
import type { ZodError, ZodType } from 'zod';

export interface ValidationPluginOptions<Types extends SchemaTypes> {
  validationError?: ValidationErrorFn<Types>;
}

export type ValidationErrorFn<Types extends SchemaTypes> = (
  error: ZodError,
  args: Record<string, unknown>,
  context: Types['Context'],
  info: GraphQLResolveInfo,
) => Error | string;

export type Constraint<T> =
  | T
  | (T extends object
      ? [value: T, options: { message?: string; path?: string[] }]
      : [value: T, options: { message?: string }]);

export type RefineConstraint<T = unknown> =
  | Constraint<(value: T) => MaybePromise<boolean>>
  | Constraint<(value: T) => MaybePromise<boolean>>[];
export interface BaseValidationOptions<T = unknown> {
  refine?: RefineConstraint<T>;
  schema?: ZodType<T>;
  type?: string;
}
export interface NumberValidationOptions<T extends number = number>
  extends BaseValidationOptions<T> {
  type?: 'number';
  min?: Constraint<number>;
  max?: Constraint<number>;
  positive?: Constraint<boolean>;
  nonnegative?: Constraint<boolean>;
  negative?: Constraint<boolean>;
  nonpositive?: Constraint<boolean>;
  int?: Constraint<boolean>;
}

export interface BigIntValidationOptions<T extends bigint = bigint>
  extends BaseValidationOptions<T> {
  type?: 'bigint';
}

export interface BooleanValidationOptions<T extends boolean = boolean>
  extends BaseValidationOptions<T> {
  type?: 'boolean';
}

export interface DateValidationOptions<T extends Date = Date> extends BaseValidationOptions<T> {
  type?: 'date';
}

export interface StringValidationOptions<T extends string = string>
  extends BaseValidationOptions<T> {
  type?: 'string';
  minLength?: Constraint<number>;
  maxLength?: Constraint<number>;
  length?: Constraint<number>;
  url?: Constraint<boolean>;
  uuid?: Constraint<boolean>;
  email?: Constraint<boolean>;
  regex?: Constraint<RegExp>;
}

export interface ObjectValidationOptions<T extends object = object>
  extends BaseValidationOptions<T> {
  type?: 'object';
}

export interface ArrayValidationOptions<T extends unknown[] = unknown[]>
  extends BaseValidationOptions<T> {
  type?: 'array';
  items?: ValidationOptions<T[number]>;
  minLength?: Constraint<number>;
  maxLength?: Constraint<number>;
  length?: Constraint<number>;
}

export type ValidationOptions<T> =
  | RefineConstraint<T>
  | RefineConstraint<T>[]
  | (T extends number
      ? NumberValidationOptions<T>
      : T extends bigint
        ? BigIntValidationOptions<T>
        : T extends boolean
          ? BooleanValidationOptions<T>
          : T extends string
            ? StringValidationOptions<T>
            : T extends Date
              ? DateValidationOptions<T>
              : T extends unknown[]
                ? ArrayValidationOptions<T>
                : T extends object
                  ? ObjectValidationOptions<T>
                  : BaseValidationOptions<T>);

export type ValidationOptionUnion =
  | ArrayValidationOptions
  | BaseValidationOptions
  | BigIntValidationOptions
  | BooleanValidationOptions
  | DateValidationOptions
  | NumberValidationOptions
  | ObjectValidationOptions
  | StringValidationOptions;
